//SPDX-FileCopyrightText: Copyright 2025-2025 深圳市同心圆网络有限公司
//SPDX-License-Identifier: GPL-3.0-only

package main

import (
	"context"
	"errors"
	"fmt"
	"net"
	"os"
	"os/signal"
	"syscall"
	"time"

	"gitcode.com/eteam/api-server/admin_api_serv/admin_auth_api_serv"
	"gitcode.com/eteam/api-server/admin_api_serv/ai_provider_admin_api_serv"
	"gitcode.com/eteam/api-server/api_serv/ai_provider_api_serv"
	"gitcode.com/eteam/api-server/api_serv/board_api_serv"
	"gitcode.com/eteam/api-server/api_serv/board_chat_api_serv"
	"gitcode.com/eteam/api-server/api_serv/board_draw_api_serv"
	"gitcode.com/eteam/api-server/api_serv/board_extdata_api_serv"
	"gitcode.com/eteam/api-server/api_serv/board_member_api_serv"
	"gitcode.com/eteam/api-server/api_serv/config_api_serv"
	"gitcode.com/eteam/api-server/api_serv/fs_api_serv"
	"gitcode.com/eteam/api-server/api_serv/user_api_serv"
	"gitcode.com/eteam/api-server/config"
	"gitcode.com/eteam/api-server/dao"
	"gitcode.com/eteam/api-server/notice_serv"
	"gitcode.com/eteam/api-server/upgrade"
	"gitcode.com/eteam/proto-gen-go.git/admin_auth_api"
	"gitcode.com/eteam/proto-gen-go.git/ai_provider_api"
	"gitcode.com/eteam/proto-gen-go.git/board_api"
	"gitcode.com/eteam/proto-gen-go.git/board_chat_api"
	"gitcode.com/eteam/proto-gen-go.git/board_draw_api"
	"gitcode.com/eteam/proto-gen-go.git/board_extdata_api"
	"gitcode.com/eteam/proto-gen-go.git/board_member_api"
	"gitcode.com/eteam/proto-gen-go.git/config_api"
	"gitcode.com/eteam/proto-gen-go.git/fs_api"
	"gitcode.com/eteam/proto-gen-go.git/user_api"
	grpc_middleware "github.com/grpc-ecosystem/go-grpc-middleware"
	grpc_zap "github.com/grpc-ecosystem/go-grpc-middleware/logging/zap"
	grpc_recovery "github.com/grpc-ecosystem/go-grpc-middleware/recovery"
	"github.com/redis/go-redis/v9"
	"go.mongodb.org/mongo-driver/mongo"
	"go.mongodb.org/mongo-driver/mongo/options"
	"go.opentelemetry.io/contrib/instrumentation/go.mongodb.org/mongo-driver/mongo/otelmongo"
	"go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc"
	"go.uber.org/zap"
	"go.uber.org/zap/zapcore"
	"google.golang.org/grpc"
	"gopkg.in/natefinch/lumberjack.v2"
)

var grpcServer *grpc.Server

func connMongoDb(dbUrl, dbName string) (*mongo.Database, error) {
	clientOpts := options.Client().ApplyURI(dbUrl).SetConnectTimeout(10 * time.Second)
	clientOpts.Monitor = otelmongo.NewMonitor()
	client, err := mongo.Connect(context.Background(), clientOpts)
	if err != nil {
		return nil, err
	}
	return client.Database(dbName), nil
}

func runGrpcServer(servCfg *config.ServerConfig, grpcLogger *zap.Logger, exitChan chan error) {
	optionList := []grpc.ServerOption{
		grpc.StreamInterceptor(
			grpc_middleware.ChainStreamServer(
				grpc_zap.StreamServerInterceptor(grpcLogger),
				grpc_recovery.StreamServerInterceptor(),
			)),
		grpc.UnaryInterceptor(
			grpc_middleware.ChainUnaryServer(
				grpc_zap.UnaryServerInterceptor(grpcLogger),
				grpc_recovery.UnaryServerInterceptor(),
			)),
	}
	if servCfg.Trace.Addr != "" {
		optionList = append(optionList, grpc.StatsHandler(otelgrpc.NewServerHandler()))
	}
	grpcServer = grpc.NewServer(optionList...)

	config_api.RegisterConfigApiServer(grpcServer, config_api_serv.NewConfigApiImpl(servCfg))
	user_api.RegisterUserApiServer(grpcServer, user_api_serv.NewUserApiImpl(&servCfg.User))
	fs_api.RegisterFsApiServer(grpcServer, fs_api_serv.NewProjectFsApiImpl(servCfg.DataBase.FileStore.RemoteAddr))
	board_api.RegisterBoardApiServer(grpcServer, &board_api_serv.BoardApiImpl{})
	board_member_api.RegisterBoardMemberApiServer(grpcServer, &board_member_api_serv.BoardMemberApiImpl{})
	board_draw_api.RegisterBoardDrawApiServer(grpcServer, &board_draw_api_serv.BoardDrawApiImpl{})
	board_chat_api.RegisterBoardChatApiServer(grpcServer, &board_chat_api_serv.BoardChatApiImpl{})
	board_extdata_api.RegisterBoardExtDataApiServer(grpcServer, &board_extdata_api_serv.BoardExtDataApiImpl{})
	ai_provider_api.RegisterAiProviderApiServer(grpcServer, &ai_provider_api_serv.AiProviderApiImpl{})

	if servCfg.Admin.Enable {
		admin_auth_api.RegisterAdminAuthApiServer(grpcServer, admin_auth_api_serv.NewAdminAuthApiImpl(servCfg.Admin.SuperPubKey))
		ai_provider_api.RegisterAiProviderAdminApiServer(grpcServer, &ai_provider_admin_api_serv.AiProviderAdminApiImpl{})
	}
	addr, err := net.ResolveTCPAddr("tcp4", fmt.Sprintf("0.0.0.0:%d", servCfg.GrpcPort))
	if err != nil {
		exitChan <- err
		return
	}
	li, err := net.ListenTCP("tcp4", addr)
	if err != nil {
		exitChan <- err
		return
	}
	fmt.Println("server start......")
	err = grpcServer.Serve(li)
	if err != nil {
		exitChan <- err
		return
	}
}

func initLogger(logpath string, lv zapcore.Level) *zap.Logger {
	hook := lumberjack.Logger{
		Filename:   logpath,
		LocalTime:  true,
		MaxSize:    100,
		MaxBackups: 30,
		MaxAge:     30,
		Compress:   false,
	}

	encoderConfig := zapcore.EncoderConfig{
		TimeKey:        "time",
		LevelKey:       "level",
		NameKey:        "logger",
		CallerKey:      "linenum",
		MessageKey:     "msg",
		StacktraceKey:  "stacktrace",
		LineEnding:     zapcore.DefaultLineEnding,
		EncodeLevel:    zapcore.CapitalLevelEncoder,
		EncodeTime:     zapcore.ISO8601TimeEncoder,
		EncodeDuration: zapcore.SecondsDurationEncoder,
		EncodeCaller:   zapcore.FullCallerEncoder,
		EncodeName:     zapcore.FullNameEncoder,
	}

	core := zapcore.NewCore(
		zapcore.NewJSONEncoder(encoderConfig),
		zapcore.AddSync(&hook),
		lv,
	)
	return zap.New(core)
}

func main() {
	if len(os.Args) != 2 {
		fmt.Printf("Usage:%s [config file]\n", os.Args[0])
		return
	}
	servCfg, err := config.ReadConfig(os.Args[1])
	if err != nil {
		fmt.Println(err)
		return
	}

	if servCfg.Trace.Addr != "" {
		//init trace
		shutDownFn, err := setupOtel(&servCfg.Trace, "eteamApi", "0.1.1")
		if err != nil {
			fmt.Println(err)
			return
		}

		defer shutDownFn(context.Background())
	}
	//init logger
	grpcLogger := initLogger(servCfg.Logging.Grpc, zap.ErrorLevel)
	defer grpcLogger.Sync()

	go func() {
		for {
			time.Sleep(30 * time.Second)
			grpcLogger.Sync()
		}
	}()

	mongoDb, err := connMongoDb(servCfg.DataBase.Mongo.Url, servCfg.DataBase.Mongo.DataBase)
	if err != nil {
		fmt.Println(err)
		return
	}
	rdb := redis.NewClient(&redis.Options{
		Addr:     servCfg.DataBase.Redis.Addr,
		Password: servCfg.DataBase.Redis.PassWord,
	})

	//init dao
	err = dao.InitDao(mongoDb, rdb)
	if err != nil {
		fmt.Println(err)
		return
	}

	err = upgrade.UpgradeDb()
	if err != nil {
		fmt.Println(err)
		return
	}

	//notice service
	err = notice_serv.Init(rdb)
	if err != nil {
		fmt.Println(err)
		return
	}

	exitChan := make(chan error, 10)
	go func() {
		sigChan := make(chan os.Signal, 1)
		signal.Notify(sigChan, syscall.SIGINT, syscall.SIGTERM, syscall.SIGQUIT)
		<-sigChan
		exitChan <- errors.New("catch signal")
	}()

	go runGrpcServer(servCfg, grpcLogger, exitChan)
	err = <-exitChan
	fmt.Println(err)
	if grpcServer != nil {
		fmt.Println("graceful stop server")
		grpcServer.GracefulStop()
		fmt.Println("wait exiting.....")
		time.Sleep(3 * time.Second)
	}
}
